# Remove the standard version of Django 
import sys
for k in [k for k in sys.modules if k.startswith('django')]: 
    del sys.modules[k] 
	
from google.appengine.dist import use_library
use_library('django', '1.2')

from google.appengine.api import users
from google.appengine.api import memcache
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
import os, random, pickle
from google.appengine.ext.webapp import template



## 
# CounterShard class
# used to efficiently store counts of things
# so that we don't have to loop over records
# or run big queries to do efficient record counts 
SHARDS_PER_COUNTER = 8
class CounterShard(db.Model):
	name = db.StringProperty(required=True)
	count = db.IntegerProperty(default=0)

##
# @param nameOfCounter the unique identifying name of the counter to update
# @return integer value of counter
def GetCount(nameOfCounter):
	memcache_id = '/CounterShard/%s' %  nameOfCounter
	result = memcache.get(memcache_id)
	if not (result == None):
		return result
	result = 0
	for shard in CounterShard.gql('WHERE name=:1', nameOfCounter):
		result += shard.count
	memcache.set(memcache_id, result, 60)
	return result

##
# @param nameOfCounter the unique identifying name of the counter to update
# @param delta the value (positive or negative) to add to the counter value
def ChangeCount(nameOfCounter, delta):
	shard_id = '/%s/%s' % (
	nameOfCounter, random.randint(1, SHARDS_PER_COUNTER))
	# embedded function update so we can run it in a single Google transaction
	def update():
		shard = CounterShard.get_by_key_name(shard_id)
		if shard:
			shard.count += delta
		else:
			shard = CounterShard(key_name=shard_id, name=nameOfCounter, count=delta)
		shard.put()
	# run the above atomically to avoid race condition errors
	db.run_in_transaction(update)

##
# DictProperty class
# Allows a Dictionary object to be stored in Google's datastore
# as a member of a db.Model class
class DictProperty(db.Property):
    data_type = dict

    def get_value_for_datastore(self, model_instance):
        value = super(DictProperty, self).get_value_for_datastore(model_instance)
        return db.Blob(pickle.dumps(value))

    def make_value_from_datastore(self, value):
        if value is None:
            return dict()
        return pickle.loads(value)

    def default_value(self):
        if self.default is None:
            return dict()
        else:
            return super(DictProperty, self).default_value().copy()

    def validate(self, value):
        if not isinstance(value, dict):
            raise db.BadValueError('Property %s needs to be convertible '
                                   'to a dict instance (%s) of class dict' % (self.name, value))
        return super(DictProperty, self).validate(value)

    def empty(self, value):
        return value is None
		
##
# AdminUser
# This class represents an individual user who can be an admin and own Talks
class AdminUser(db.Model):
	google_user = db.UserProperty(required=True)
	email_address = db.EmailProperty()
	avatar_image = db.BlobProperty() # reserved for future use to hold image data of user's avatar
	avatar_image_url = db.LinkProperty() # reserved for future use to hold a URL that will serve the user's avatar

	
## 
# Topic Class
# 
class Topic(db.Model):
	creator = db.ReferenceProperty(reference_class=AdminUser, collection_name='topic_creator')
	created_date = db.DateTimeProperty(auto_now_add=True)
	title = db.StringProperty(required=True, default="Title of Topic")
	description = db.StringProperty(required=True, default="Brief description of topic")
	visible = db.BooleanProperty(required=True, default=True)
	num_rankings = db.IntegerProperty(default=0)
	avg_ranking = db.FloatProperty(default=float(0))
	num_very_interested = db.IntegerProperty(default=0)
	num_ratings = db.IntegerProperty(default=0)
	avg_rating = db.FloatProperty(default=float(0))
	last_updated = db.DateTimeProperty(auto_now=True)
	

## 
# Talk Class
# This is the class that represents one specific "talk" or presentation delivered
# by one presenter on one occasion.  It holds references to all the Topics
# that may be presented during the Talk and all the AudienceStars who attended
# and participated in the Talk
class Talk(db.Model):
	title = db.StringProperty(required=True) # the full title that will appear on pages as text
	short_title = db.StringProperty(required=True) # the abbreviated version of the title that will be used in the URL
	description = db.StringProperty(default="Description of presentation goes here.")
	icon = db.BlobProperty()
	icon_url = db.StringProperty(default="/static/info.png")
	owner = db.ReferenceProperty(reference_class=AdminUser, collection_name='talk_owner') # the one admin user who owns this Talk
	admin_users = db.ListProperty(db.Key) # list of other AdminUsers who can modify and control this Talk
	state = db.StringProperty(required=True, choices=["NOT_STARTED", "RANKING", "STARTED", "FINISHED", "ARCHIVED"], default="NOT_STARTED")
	topics = db.ListProperty(db.Key) # list of keys to the Topics that may be presented during this Talk
	current_topic = db.ReferenceProperty(reference_class=Topic, collection_name='current_topic') # the one Topic that is currently being discussed
	stars = db.ListProperty(db.Key) # list of keys to the Stars (audience members) who registered for this Talk
	category1 = db.StringProperty(required=True, default="Game Developer")
	category2 = db.StringProperty(required=True, default="Government")
	category3 = db.StringProperty(required=True, default="Other")
	
##
# Star Class
# The Star class represents a member of the audience who may
# rank topics in a talk, rate topics in a talk, contribute comments 
# to a topic in a talk, answer survey questions (yes/no) or 
# ask questions of the presenter.
class Star(db.Model):
	session_id = db.StringProperty() # The unique session id generated for this user
	talk = db.ReferenceProperty(reference_class=Talk, collection_name="talk_for_star")
	display_name = db.StringProperty() # The name displayed on screen to other users
	email_address = db.EmailProperty() # optional email address of user (for now, not used)
	sex = db.StringProperty(required=True, choices=["M", "F", "U"], default="U")
	age = db.IntegerProperty(required=True, default=0)
	category = db.IntegerProperty(required=True, choices=[1,2,3], default=3) # category represents grouping into one of three groups (where group 3 is usually 'other')
	avatar_image = db.BlobProperty() # reserved for future use to hold image data of user's avatar
	avatar_image_url = db.LinkProperty() # reserved for future use to hold a URL that will serve the user's avatar
	awards = db.StringListProperty() # list of achievements earned by this user
	connected = db.BooleanProperty(default=True)
	

##
# Presenter Class
class Presenter(db.Model):
	session_id = db.StringProperty() # The unique session id generated for this user
	talk = db.ReferenceProperty(reference_class=Talk, collection_name="talk_for_presenter")
	
##
# TopicRanking Class
# Stores users' rankings (-1, 0, 1) of interest in each topic
# One record per user per topic
class TopicRanking(db.Model):
	topic = db.ReferenceProperty(reference_class=Topic, required=True, collection_name="rankings_for_topic")
	star = db.ReferenceProperty(reference_class=Star, required=True, collection_name="topic_rankings_by_star")
	value = db.IntegerProperty(required=True, choices=[-1, 0, 1])
	
##
# TopicRating Class
# Stores users' ratings of satisfaction in each topic
# One record per user per topic
class TopicRating(db.Model):
	topic = db.ReferenceProperty(reference_class=Topic, required=True, collection_name="ratings_for_topic")
	star = db.ReferenceProperty(reference_class=Star, required=True, collection_name="topic_ratings_by_star")
	value = db.IntegerProperty(required=True)
	
	
## 
# CommentQuestion Class
class CommentQuestion(db.Model):
	talk = db.ReferenceProperty(reference_class=Talk, required=True, collection_name="commnts_for_talk")
	topic = db.ReferenceProperty(reference_class=Topic, required=True, collection_name="comments_for_topic")
	star = db.ReferenceProperty(reference_class=Star, required=True, collection_name="comments_by_star")
	type = db.StringProperty(required=True, choices=["question", "comment"])
	text = db.TextProperty()
	answered = db.BooleanProperty(default=False) # set to True when presenter indicates this question has been addressed
	created_date = db.DateTimeProperty(auto_now_add=True)
	
##
# Achievement Class
# Holds titles and descriptions of Achievements
class Achievement(db.Model):
	title = db.StringProperty(required=True)
	description = db.StringProperty(required=True, default="Achievement description goes here.")
	
##	
# Email message queued to send or sent
class EmailMsg(db.Model):
	generated_by = db.ReferenceProperty(reference_class=AdminUser, collection_name='admin_user_gen_email')
	msg_sender = db.EmailProperty(default="no-reply@audiencestar.appspotmail.com")
	msg_to = db.EmailProperty(default="rpargman@gmail.com")
	msg_subject = db.StringProperty(default="Message from AudienceStar")
	msg_body = db.TextProperty()
	enqueued_on = db.DateTimeProperty(auto_now_add=True)
	sent = db.BooleanProperty(default=False)
	last_update = db.DateTimeProperty(auto_now=True)
	error_msg = db.TextProperty()
	
def SendEmailLater(sender=None, to=None, subject=None, body=None):
	user = users.get_current_user()
	if user:
		user_query = db.GqlQuery("SELECT * FROM AdminUser WHERE google_user = :1", user)
		admin_user = user_query.get()
		if admin_user:
			msg = EmailMsg(generated_by=admin_user)
			if to:
				msg.msg_to = to
			if sender:
				msg.msg_sender = sender
			if subject:
				msg.msg_subject = subject
			if body:
				msg.msg_body = body
			else:
				msg.msg_body = "There is an error in AudienceStar. It was attempted to send this message to %s with no text." % msg_to
				msg.msg_to = "rpargman@gmail.com"
			msg.put()

##
# This class is used as a wrapper to the request handler to
# automatically look up and insert into the template values
# some commonly-used information.  It just saves repeating
# code in lots of other handlers to do common things like
# build a "logout" URL to use in the logout link on every page.
class BaseHandler(webapp.RequestHandler):
	def render_template(self, file, template_args):
		path = os.path.join(os.path.dirname(__file__), "templates", file)
		user, admin_user =  self.user_and_admin()
		if not template_args.has_key('user'):
			template_args['user'] = user
		if not template_args.has_key('admin_user'):
			template_args['admin_user'] = admin_user
		if not (template_args.has_key('logout_url')):
			template_args['logout_url'] = users.create_logout_url("/")
		if not (template_args.has_key('login_url')):
			template_args['login_url'] = users.create_login_url('/checklogin')
		if not (template_args.has_key('page_title')):
			template_args['page_title'] = "Audience Star"
		
		
		# Detect iPhone
		browser = str(self.request.headers['User-Agent'])
		iphone_device = False
		if (browser.find("iPhone") > -1) or (browser.find("iPod") > -1):
			iphone_device = True
		template_args['iphone_device'] = iphone_device;
		
		# Detect Android
		android_device = False
		if (browser.find("android") > -1):
			android_device = True
		template_args['android_device'] = android_device;
		
		self.response.out.write(template.render(path, template_args))
		
	def user_and_admin(self):
		user = users.get_current_user()
		admin_user = None
		if user:
			admin_query = db.GqlQuery("SELECT * FROM AdminUser WHERE google_user = :1", user)
			admin_user = admin_query.get()
		return (user, admin_user)
		


